import { Injectable, Logger } from '@nestjs/common';
import { ethers } from 'ethers';

@Injectable()
export class XLayerContractHelper {
  private readonly logger = new Logger(XLayerContractHelper.name);

  // Terigon 测试网配置 (chainId 1952)
  private readonly XL_TESTNET_RPC: string;
  private readonly XL_TESTNET_CHAIN_ID = 1952;

  // 合约配置
  private readonly LAUNCHPAD_ADDRESS: string;
  private readonly provider: ethers.JsonRpcProvider;

  // 合约 ABI (简化版本，包含主要方法)
  private readonly CONTRACT_ABI = [
    // 创建代币
    'function createTokenLaunch(string memory _name, string memory _symbol, string memory _description, string memory _website, string memory _telegram, string memory _twitter, uint256 _targetAmount) external payable',

    // 购买代币
    'function buyTokens(uint256 _launchId) external payable',

    // 出售代币
    'function sellTokens(uint256 _launchId, uint256 _tokenAmount) external',

    // 毕业代币
    'function manualGraduateToken(uint256 _launchId) external',

    // 取消发射
    'function cancelLaunch(uint256 _launchId) external',

    // 申请退款
    'function claimRefund(uint256 _launchId) external',

    // 查询函数
    'function getLaunchInfo(uint256 _launchId) external view returns (address tokenAddress, address creator, string memory name, string memory symbol, string memory description, uint256 targetAmount, uint256 raisedAmount, uint256 deadline, bool launched, bool cancelled, uint256 currentPrice, uint256 tokensSold, uint256 totalSupplyForSale)',
    'function getLaunchSocialInfo(uint256 _launchId) external view returns (string memory website, string memory telegram, string memory twitter)',
    'function getUserContribution(uint256 _launchId, address _user) external view returns (uint256)',
    'function getUserTokenBalance(uint256 _launchId, address _user) external view returns (uint256)',
    'function getContributors(uint256 _launchId) external view returns (address[] memory)',
    'function canGraduate(uint256 _launchId) external view returns (bool)',

    // 价格计算
    'function calculateTokensForETH(uint256 _launchId, uint256 _ethAmount) external view returns (uint256)',
    'function calculateETHForTokens(uint256 _launchId, uint256 _tokenAmount) external view returns (uint256)',
    'function getCurrentTokenPrice(uint256 _launchId) external view returns (uint256)',

    // 事件
    'event TokenLaunchCreated(uint256 indexed launchId, address indexed creator, address tokenAddress, string name, string symbol, uint256 targetAmount)',
    'event TokenPurchase(uint256 indexed launchId, address indexed buyer, uint256 ethAmount, uint256 tokensReceived, uint256 newPrice)',
    'event TokenSale(uint256 indexed launchId, address indexed seller, uint256 tokensAmount, uint256 ethReceived, uint256 newPrice)',
    'event TokenGraduated(uint256 indexed launchId, address indexed tokenAddress, address pairAddress, uint256 liquidityETH, uint256 liquidityTokens)',
    'event LaunchCancelled(uint256 indexed launchId, address indexed creator)',
    'event RefundClaimed(uint256 indexed launchId, address indexed contributor, uint256 amount)',
  ];

  constructor() {
    if (!process.env.XLAYER_RPC_URL) {
      throw new Error('XLAYER_RPC_URL environment variable is required');
    }
    if (!process.env.XLAYER_LAUNCHPAD_ADDRESS) {
      throw new Error(
        'XLAYER_LAUNCHPAD_ADDRESS environment variable is required',
      );
    }
    this.XL_TESTNET_RPC = process.env.XLAYER_RPC_URL;
    this.LAUNCHPAD_ADDRESS = process.env.XLAYER_LAUNCHPAD_ADDRESS.trim();
    this.provider = new ethers.JsonRpcProvider(this.XL_TESTNET_RPC);
    this.logger.log(
      `XLayer Contract Helper initialized with RPC: ${this.XL_TESTNET_RPC}, LaunchPad: ${this.LAUNCHPAD_ADDRESS}`,
    );
  }

  /**
   * 获取钱包实例
   */
  getWallet(privateKey: string): ethers.Wallet {
    const wallet = new ethers.Wallet(privateKey, this.provider);
    return wallet;
  }

  /**
   * 获取只读合约实例
   */
  getReadOnlyContract(): ethers.Contract {
    return new ethers.Contract(
      this.LAUNCHPAD_ADDRESS,
      this.CONTRACT_ABI,
      this.provider,
    );
  }

  /**
   * 获取带签名者的合约实例
   */
  getContractWithSigner(privateKey: string): ethers.Contract {
    const wallet = this.getWallet(privateKey);
    return new ethers.Contract(
      this.LAUNCHPAD_ADDRESS,
      this.CONTRACT_ABI,
      wallet,
    );
  }

  /**
   * 创建 Launch (链上)
   */
  async createLaunch(params: {
    name: string;
    symbol: string;
    description: string;
    website?: string;
    telegram?: string;
    twitter?: string;
    targetEth: string; // human readable
    creatorPrivateKey: string;
    creationFeeEth?: string; // 默认 0.02
  }): Promise<{
    txHash: string;
    launchId: bigint;
    tokenAddress: string;
    creator: string;
  }> {
    const creationFee = params.creationFeeEth
      ? ethers.parseEther(params.creationFeeEth)
      : ethers.parseEther('0.02');
    const targetWei = ethers.parseEther(params.targetEth);
    const contract = this.getContractWithSigner(params.creatorPrivateKey);
    const walletAddr =
      (contract.runner as any)?.address ||
      (await (contract.runner as any).getAddress?.());
    this.logger.log(
      `Creating launch by ${walletAddr} target=${params.targetEth} ETH fee=${ethers.formatEther(creationFee)}`,
    );
    const tx = await contract.createTokenLaunch(
      params.name,
      params.symbol,
      params.description,
      params.website || '',
      params.telegram || '',
      params.twitter || '',
      targetWei,
      { value: creationFee },
    );
    this.logger.log(`createTokenLaunch sent tx=${tx.hash}`);
    const receipt = await tx.wait();
    if (!receipt) throw new Error('No receipt for createTokenLaunch');

    this.logger.log(`Transaction confirmed with ${receipt.logs.length} logs`);

    const iface = new ethers.Interface(this.CONTRACT_ABI);
    let launchId: bigint | null = null;
    let tokenAddr = '';
    for (const log of receipt.logs) {
      try {
        const parsed = iface.parseLog(log);
        this.logger.log(
          `Parsed log: ${parsed?.name}, args: ${JSON.stringify(parsed?.args)}`,
        );
        if (parsed && parsed.name === 'TokenLaunchCreated') {
          launchId = parsed.args.launchId as bigint;
          tokenAddr = parsed.args.tokenAddress as string;
          this.logger.log(
            `Found TokenLaunchCreated: launchId=${launchId}, tokenAddress=${tokenAddr}`,
          );
          break;
        }
      } catch (err) {
        this.logger.warn(`Failed to parse log: ${err.message}`);
      }
    }
    if (launchId === null)
      throw new Error('TokenLaunchCreated event not found');

    this.logger.log(
      `Returning: launchId=${launchId}, tokenAddress=${tokenAddr}, creator=${walletAddr}`,
    );
    return {
      txHash: tx.hash,
      launchId,
      tokenAddress: tokenAddr,
      creator: walletAddr,
    };
  }

  /**
   * 购买代币 (buyTokens)
   */
  async buyTokens(params: {
    launchId: bigint | number;
    ethAmount: string;
    buyerPrivateKey: string;
  }): Promise<{ txHash: string }> {
    const contract = this.getContractWithSigner(params.buyerPrivateKey);
    const value = ethers.parseEther(params.ethAmount);
    const tx = await contract.buyTokens(params.launchId, { value });
    this.logger.log(
      `buyTokens launchId=${params.launchId} value=${params.ethAmount} tx=${tx.hash}`,
    );
    const receipt = await tx.wait();
    let tokensReceived: bigint | null = null;
    let newPrice: bigint | null = null;
    if (receipt) {
      const iface = new ethers.Interface(this.CONTRACT_ABI);
      for (const log of receipt.logs) {
        try {
          const parsed = iface.parseLog(log);
          if (parsed && parsed.name === 'TokenPurchase') {
            tokensReceived = parsed.args.tokensReceived as bigint;
            newPrice = parsed.args.newPrice as bigint;
            break;
          }
        } catch (_) {}
      }
    }
    return {
      txHash: tx.hash,
      tokensReceived: tokensReceived ?? 0n,
      newPrice: newPrice ?? 0n,
    } as any;
  }

  /**
   * 出售代币 (sellTokens) - 自动 approve
   */
  async sellTokens(params: {
    launchId: bigint | number;
    tokenAmount: string;
    sellerPrivateKey: string;
  }): Promise<{ txHash: string }> {
    const contract = this.getContractWithSigner(params.sellerPrivateKey);
    // 获取 tokenAddress
    const info = await contract.getLaunchInfo(params.launchId);
    const tokenAddress: string = info[0];
    if (!tokenAddress || tokenAddress === ethers.ZeroAddress)
      throw new Error('Invalid token address');
    const walletAddr =
      (contract.runner as any)?.address ||
      (await (contract.runner as any).getAddress?.());
    const erc20 = new ethers.Contract(
      tokenAddress,
      [
        'function balanceOf(address) view returns (uint256)',
        'function approve(address,uint256) returns (bool)',
        'function allowance(address,address) view returns (uint256)',
      ],
      contract.runner!,
    );
    const amount = ethers.parseUnits(params.tokenAmount, 18);
    const allowance: bigint = await erc20.allowance(
      walletAddr,
      this.LAUNCHPAD_ADDRESS,
    );
    if (allowance < amount) {
      const approveTx = await erc20.approve(this.LAUNCHPAD_ADDRESS, amount);
      this.logger.log(`approve tx=${approveTx.hash}`);
      await approveTx.wait();
    }
    const tx = await contract.sellTokens(params.launchId, amount);
    this.logger.log(
      `sellTokens launchId=${params.launchId} amount=${params.tokenAmount} tx=${tx.hash}`,
    );
    const receipt = await tx.wait();
    let ethReceived: bigint | null = null;
    let newPrice: bigint | null = null;
    if (receipt) {
      const iface = new ethers.Interface(this.CONTRACT_ABI);
      for (const log of receipt.logs) {
        try {
          const parsed = iface.parseLog(log);
          if (parsed && parsed.name === 'TokenSale') {
            ethReceived = parsed.args.ethReceived as bigint;
            newPrice = parsed.args.newPrice as bigint;
            break;
          }
        } catch (_) {}
      }
    }
    return {
      txHash: tx.hash,
      ethReceived: ethReceived ?? 0n,
      newPrice: newPrice ?? 0n,
    } as any;
  }

  /**
   * 取消发射 (cancelLaunch)
   */
  async cancelLaunch(params: {
    launchId: bigint | number;
    creatorPrivateKey: string;
  }): Promise<{ txHash: string }> {
    const contract = this.getContractWithSigner(params.creatorPrivateKey);
    const walletAddr =
      (contract.runner as any)?.address ||
      (await (contract.runner as any).getAddress?.());

    // 验证是否是创建者
    const info = await contract.getLaunchInfo(params.launchId);
    const creator: string = info[1];
    if (creator.toLowerCase() !== walletAddr.toLowerCase()) {
      throw new Error('Only the creator can cancel the launch');
    }

    // 检查是否已经取消或已发射
    const cancelled: boolean = info[9];
    const launched: boolean = info[8];
    if (cancelled) {
      throw new Error('Launch already cancelled');
    }
    if (launched) {
      throw new Error('Cannot cancel a launched token');
    }

    this.logger.log(
      `Cancelling launch ${params.launchId} by creator ${walletAddr}`,
    );

    const tx = await contract.cancelLaunch(params.launchId);
    this.logger.log(`cancelLaunch launchId=${params.launchId} tx=${tx.hash}`);

    const receipt = await tx.wait();
    if (!receipt) throw new Error('No receipt for cancelLaunch');

    // 验证取消事件
    const iface = new ethers.Interface(this.CONTRACT_ABI);
    let cancelConfirmed = false;
    for (const log of receipt.logs) {
      try {
        const parsed = iface.parseLog(log);
        if (parsed && parsed.name === 'LaunchCancelled') {
          cancelConfirmed = true;
          break;
        }
      } catch (_) {
        /* ignore non-matching logs */
      }
    }

    if (!cancelConfirmed) {
      this.logger.warn(`LaunchCancelled event not found in tx ${tx.hash}`);
    }

    return { txHash: tx.hash };
  }

  /**
   * 申请退款 (claimRefund)
   */
  async claimRefund(params: {
    launchId: bigint | number;
    contributorPrivateKey: string;
  }): Promise<{ txHash: string; refundAmount?: bigint }> {
    const contract = this.getContractWithSigner(params.contributorPrivateKey);
    const walletAddr =
      (contract.runner as any)?.address ||
      (await (contract.runner as any).getAddress?.());

    // 检查 launch 状态
    const info = await contract.getLaunchInfo(params.launchId);
    const cancelled: boolean = info[9];
    const launched: boolean = info[8];

    if (!cancelled) {
      throw new Error('Cannot claim refund: launch is not cancelled');
    }
    if (launched) {
      throw new Error('Cannot claim refund: token has already launched');
    }

    // 检查用户是否有贡献
    const contribution = await contract.getUserContribution(
      params.launchId,
      walletAddr,
    );
    if (contribution === 0n) {
      throw new Error('No contribution found for this address');
    }

    this.logger.log(
      `Claiming refund for launch ${params.launchId} by ${walletAddr}, contribution: ${ethers.formatEther(contribution)} ETH`,
    );

    const tx = await contract.claimRefund(params.launchId);
    this.logger.log(`claimRefund launchId=${params.launchId} tx=${tx.hash}`);

    const receipt = await tx.wait();
    if (!receipt) throw new Error('No receipt for claimRefund');

    // 解析退款事件
    const iface = new ethers.Interface(this.CONTRACT_ABI);
    let refundAmount: bigint | null = null;
    for (const log of receipt.logs) {
      try {
        const parsed = iface.parseLog(log);
        if (parsed && parsed.name === 'RefundClaimed') {
          refundAmount = parsed.args.amount as bigint;
          break;
        }
      } catch (_) {
        /* ignore non-matching logs */
      }
    }

    return {
      txHash: tx.hash,
      refundAmount: refundAmount ?? contribution,
    };
  }

  /**
   * 格式化 ETH 数量
   */
  static formatETH(weiAmount: string): string {
    return ethers.formatEther(weiAmount);
  }

  /**
   * 解析 ETH 数量到 wei
   */
  static parseETH(ethAmount: string): bigint {
    return ethers.parseEther(ethAmount);
  }

  /**
   * 格式化代币数量
   */
  static formatTokens(tokenAmount: string): string {
    return ethers.formatUnits(tokenAmount, 18);
  }

  /**
   * 解析代币数量
   */
  static parseTokens(tokenAmount: string): bigint {
    return ethers.parseUnits(tokenAmount, 18);
  }

  /**
   * 获取当前gas价格
   */
  async getGasPrice(): Promise<bigint> {
    try {
      const feeData = await this.provider.getFeeData();
      return feeData.gasPrice || ethers.parseUnits('20', 'gwei');
    } catch (error) {
      this.logger.warn('Failed to get gas price, using default', error);
      return ethers.parseUnits('20', 'gwei');
    }
  }

  /**
   * 获取网络信息
   */
  async getNetworkInfo() {
    try {
      const network = await this.provider.getNetwork();
      const blockNumber = await this.provider.getBlockNumber();
      const gasPrice = await this.getGasPrice();

      return {
        chainId: Number(network.chainId),
        name: network.name,
        blockNumber,
        gasPrice: ethers.formatUnits(gasPrice, 'gwei'),
      };
    } catch (error) {
      this.logger.error('Failed to get network info', error);
      throw error;
    }
  }

  /**
   * 验证地址格式
   */
  static isValidAddress(address: string): boolean {
    return ethers.isAddress(address);
  }

  /**
   * 获取地址余额
   */
  async getBalance(address: string): Promise<string> {
    try {
      const balance = await this.provider.getBalance(address);
      return ethers.formatEther(balance);
    } catch (error) {
      this.logger.error(`Failed to get balance for ${address}`, error);
      throw error;
    }
  }

  /**
   * 等待交易确认
   */
  async waitForTransaction(
    txHash: string,
    confirmations = 1,
  ): Promise<ethers.TransactionReceipt> {
    try {
      const receipt = await this.provider.waitForTransaction(
        txHash,
        confirmations,
      );
      if (!receipt) {
        throw new Error('Transaction not found');
      }
      return receipt;
    } catch (error) {
      this.logger.error(`Failed to wait for transaction ${txHash}`, error);
      throw error;
    }
  }

  /**
   * 获取交易详情
   */
  async getTransaction(
    txHash: string,
  ): Promise<ethers.TransactionResponse | null> {
    try {
      return await this.provider.getTransaction(txHash);
    } catch (error) {
      this.logger.error(`Failed to get transaction ${txHash}`, error);
      return null;
    }
  }

  /**
   * 更新合约地址
   */
  updateContractAddress(newAddress: string) {
    if (!XLayerContractHelper.isValidAddress(newAddress)) {
      throw new Error('Invalid contract address');
    }
    (this as any).LAUNCHPAD_ADDRESS = newAddress;
    this.logger.log(`Contract address updated to: ${newAddress}`);
  }

  /**
   * 获取当前合约地址
   */
  getContractAddress(): string {
    return this.LAUNCHPAD_ADDRESS;
  }

  /** 获取链上 Launch 详情并结构化 */
  async getLaunchInfo(launchId: bigint | number) {
    const contract = this.getReadOnlyContract();
    const info = await contract.getLaunchInfo(launchId);
    // 按 ABI 顺序解构
    return {
      tokenAddress: info[0] as string,
      creator: info[1] as string,
      name: info[2] as string,
      symbol: info[3] as string,
      description: info[4] as string,
      targetAmount: info[5] as bigint,
      raisedAmount: info[6] as bigint,
      deadline: Number(info[7]),
      launched: info[8] as boolean,
      cancelled: info[9] as boolean,
      currentPrice: info[10] as bigint,
      tokensSold: info[11] as bigint,
      totalSupplyForSale: info[12] as bigint,
    };
  }
}

// 合约配置常量
export const XL_CONTRACT_CONFIG = {
  LAUNCHPAD_ADDRESS: '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9', // hardhat 本地部署地址
  CHAIN_ID: 1952,
  RPC_URL: 'https://testrpc.xlayer.tech/terigon',
  BLOCK_EXPLORER: 'https://www.oklink.com/xlayer-test',

  // 联合曲线参数
  CURVE_BASE_PRICE: '0.0001', // 0.0001 ETH
  CURVE_PRICE_INCREMENT: '0.00001', // 0.00001 ETH
  TOKENS_FOR_CURVE: '800000000', // 8亿代币
  TOKENS_FOR_LIQUIDITY: '200000000', // 2亿代币

  // 平台参数
  MIN_TARGET: '1', // 1 ETH
  MAX_TARGET: '100', // 100 ETH
  GRADUATION_THRESHOLD: 80, // 80%
  PLATFORM_FEE: 200, // 2%
  LAUNCH_DURATION: 7 * 24 * 60 * 60, // 7 days in seconds

  // Gas 配置
  GAS_LIMIT: {
    CREATE_TOKEN: 2000000,
    BUY_TOKENS: 300000,
    SELL_TOKENS: 200000,
    GRADUATE: 500000,
    CANCEL: 100000,
    REFUND: 150000,
  },
};
